using System;
using System.Collections.Generic;
using NobleTech.Products.CreatureKingdom.Utils;

namespace NobleTech.Products.CreatureKingdom
{
    internal class OrganismAbilities
    {
        private OrganismData data;
        private OrganismStats stats;
        private OrganismInfo info;

        public OrganismAbilities(Guid id, Type implementationType,
            PointD location, OrganismStats stats)
        {
            this.stats = stats;
            this.data = new OrganismData(location, stats.Stamina);
            this.info = new OrganismInfo(id, implementationType, stats.BornSize);
        }

        public OrganismInfo Info { get { return info; } }

        public Guid ID { get { return info.ID; } }
        public PointD Location { get { return data.Location; } }
        public double Size { get { return info.Size; } }

        // This gets a copy of the OrganismInfo that can be passed to implementations
        public OrganismInfo GetInfoCopy() { return new OrganismInfo(info); }

        public bool GivingBirth { get { return data.ChildDue == 0; } }
        public double Energy { get { return data.Age == stats.LifeSpan ? 0 : data.Energy; } }
        public bool Alive { get { return info.Alive = Energy > 0; } }
        public bool CanDoLoving { get { return stats.Gender == OrganismStats.Genders.Male; } }

        public uint MaxInteractions { get { return stats.MaxInteractions; } }
        public double SightRange { get { return stats.SightRange; } }
        public double AttackRange { get { return stats.AttackRange; } }
        public double Weight { get { return Size; } }
        public double Strength { get { return stats.MaxStrength; } }    // * data.Energy
        public double MaxSpeed(double directionFacing, double directionMoving)
        {
            double moveAngle = Utils.Misc.NormaliseAngle(directionMoving - directionFacing);
            bool movingForwards = moveAngle < Math.PI / 2 || moveAngle > Math.PI * 3 / 2;
            // Once get down to 60% energy, start slowing down
            double maxSpeedMultiplier = Math.Min(1, Energy / (stats.Stamina * 0.6));
            if (movingForwards)
                return stats.MaxSpeedForwards * maxSpeedMultiplier;
            else
                return stats.MaxSpeedBackwards * maxSpeedMultiplier;
        }

        public double GetDefence(double defendingAngle)
        {
            bool defendingFront = defendingAngle < Math.PI / 2
                || defendingAngle > Math.PI * 3 / 2;
            return defendingFront ? stats.ArmourFront : stats.ArmourBack;
        }

        public void SpendEnergy(double energy)
        {
            if (energy < 0)
                throw new ArgumentOutOfRangeException("energy", energy, "energy to spend must not be negative");
            data.Energy -= energy;
        }

        public void Injured(double injury)
        {
            data.Injury += Math.Max(0, injury);
        }

        public void Move(SizeD distance)
        {
            data.Location += distance;
        }

        public bool Impregnate(OrganismAbilities father)
        {
            // No male pregancies
            if (stats.Gender != OrganismStats.Genders.Female)
                return false;
            // If the target is pregnant then loving fails
            if (data.ChildDue != null)
                return false;
            data.ChildDue = stats.GestationPeriod;
            data.ChildsFather = father.stats;
            return true;
        }

        public void EndPregnancy() { data.ChildDue = null; }

        public bool UpdateSize()
        {
            if (data.Age > stats.FullyGrownAge)
                return false;
            // Calculate the size at a certain age
            info.Size = stats.BornSize + (stats.FullyGrownSize - stats.BornSize)
                * Math.Min(1, (float)data.Age / stats.FullyGrownAge);
            //SpendEnergy(???);
            return true;
        }

        // Calculate the value of a property that is size-related and less at the end of life
        protected double SizeRelatedValue(double val)
        {
            return val * Size * Math.Min(1, (stats.LifeSpan - data.Age)
                / (stats.LifeSpan - stats.FrailtyAge));
        }

        public void Age()
        {
            // Do aging
            ++data.Age;
            // Do gestation
            if (data.ChildDue != null)
                --data.ChildDue;
        }
    }
}
